package middle.ground.utils;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

/**
 * HTTP工具类
 *
 * @author jrl
 */
public class HttpUtils{

	public static final String DEFAULT_CHARSET = "UTF-8";

	private static final RequestConfig CONFIG = RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(30000).build();

	private static final PoolingHttpClientConnectionManager POOLING_HTTP_CLIENT_CONNECTION_MANAGER = new PoolingHttpClientConnectionManager();

	static {
		POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(200);
		POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(20);
		new Timer(true).schedule(new IdleConnectionMonitor(POOLING_HTTP_CLIENT_CONNECTION_MANAGER), 60000, 60000);
	}

	private static final ConnectionKeepAliveStrategy KEEP_ALIVE_STRATEGY = new ConnectionKeepAliveStrategy(){
		@Override
		public long getKeepAliveDuration(org.apache.http.HttpResponse response, HttpContext context) {
			HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
			while (it.hasNext()) {
				HeaderElement he = it.nextElement();
				String param = he.getName();
				String value = he.getValue();
				if (value != null && "timeout".equalsIgnoreCase(param)) {
					try {
						return Long.valueOf(value) * 1000;
					} catch (NumberFormatException e) {
						e.printStackTrace();
						break;
					}
				}
			}
			return 30 * 1000;
		}
	};

	private static CloseableHttpClient getHttpClient() {
		return HttpClients.custom().setKeepAliveStrategy(KEEP_ALIVE_STRATEGY).setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER).setConnectionManagerShared(true).build();
	}

	private static CloseableHttpClient getHttpClient4Cookie(CookieStore cookieStore) {
		return HttpClients.custom().setKeepAliveStrategy(KEEP_ALIVE_STRATEGY).setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER).setDefaultCookieStore(cookieStore).setConnectionManagerShared(true).build();
	}

	public static HttpResponse get(String url, Map<String, Object> paramsMap, Header header) {
		StringBuilder builder = new StringBuilder();
		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				builder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + builder.toString().substring(1);
		}

		HttpGet method = new HttpGet(url);
		method.setConfig(CONFIG);
		if (null != header) {
			method.setHeader(header);
		}
		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse getEntity(String url, Map<String, Object> paramsMap, Header header) {
		StringBuilder builder = new StringBuilder();
		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				builder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + builder.toString().substring(1);
		}

		HttpGet method = new HttpGet(url);
		method.setConfig(CONFIG);
		if (null != header) {
			method.setHeader(header);
		}
		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toByteArray(entity));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse get(String url, Map<String, Object> paramsMap, String headerName, String headerValue) {
		StringBuilder builder = new StringBuilder();
		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				builder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + builder.toString().substring(1);
		}

		HttpGet method = new HttpGet(url);
		method.setConfig(CONFIG);

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse getEntity(String url, Map<String, Object> paramsMap, String headerName, String headerValue) {
		StringBuilder builder = new StringBuilder();
		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				builder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + builder.toString().substring(1);
		}

		HttpGet method = new HttpGet(url);
		method.setConfig(CONFIG);

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toByteArray(entity));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse get4Cookie(String url, String name, String value) {
		StringBuilder builder = new StringBuilder();

		HttpGet method = new HttpGet(url);
		method.setConfig(CONFIG);

		CookieStore cookieStore = new BasicCookieStore();
		BasicClientCookie cookie = new BasicClientCookie(name, value);
		cookie.setDomain("sms.yc.hb.cegn.cn");
		cookie.setPath("/");
		cookieStore.addCookie(cookie);

		try (CloseableHttpResponse response = getHttpClient4Cookie(cookieStore).execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static int head(String url) {
		HttpHead method = new HttpHead(url);
		method.setConfig(CONFIG);

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			return response.getStatusLine().getStatusCode();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse post(String url, NameValuePair[] nameValuePair, Header... headers) {
		Map<String, String> paramsMap = new HashMap<String, String>(nameValuePair.length);
		for (NameValuePair t : nameValuePair) {
			paramsMap.put(t.getName(), t.getValue());
		}
		return post(url, paramsMap, null, null);
	}

	public static HttpResponse post(String url, Map<String, String> paramsMap, String name, String value) {
		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);

		if (paramsMap != null) {
			try {
				List<NameValuePair> paramList = new ArrayList<NameValuePair>();
				for (Map.Entry<String, String> param : paramsMap.entrySet()) {
					paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
				}
				method.setEntity(new UrlEncodedFormEntity(paramList, DEFAULT_CHARSET));
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException(e);
			}
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			Header lastHeader = response.getLastHeader("Set-Cookie");
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET), lastHeader.getValue());
			}

			return new HttpResponse(response.getStatusLine().getStatusCode(), lastHeader.getValue());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse post(String url, Map<String, String> paramsMap, String... charsetArray) {
		String charset = charsetArray != null && charsetArray.length > 0 ? charsetArray[0] : DEFAULT_CHARSET;
		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);
		if (paramsMap != null) {
			try {
				List<NameValuePair> paramList = new ArrayList<NameValuePair>();
				for (Map.Entry<String, String> param : paramsMap.entrySet()) {
					paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
				}
				method.setEntity(new UrlEncodedFormEntity(paramList, charset));
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException(e);
			}
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(),
						EntityUtils.toString(entity, charset));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse postWithContentType(String url, String body, String contentType,
												   String... charsetArray) {
		String charset = charsetArray != null && charsetArray.length > 0 ? charsetArray[0] : DEFAULT_CHARSET;
		StringEntity requestBody = new StringEntity(body, charset);
		requestBody.setContentType(contentType);

		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);
		method.setEntity(requestBody);

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(),
						EntityUtils.toString(entity, charset));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse post4Cookie(String url, Map<String, String> paramsMap, String name, String value, Header... headers) {
		StringBuilder builder = new StringBuilder();
		if (paramsMap != null) {
			for (Map.Entry<String, String> param : paramsMap.entrySet()) {
				builder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + builder.toString().substring(1);
		}

		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);

		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		CookieStore cookieStore = new BasicCookieStore();
		BasicClientCookie cookie = new BasicClientCookie(name, value);
		cookie.setDomain("sms.yc.hb.cegn.cn");
		cookie.setPath("/");
		cookieStore.addCookie(cookie);

		try (CloseableHttpResponse response = getHttpClient4Cookie(cookieStore).execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse post(String url, String body, String contentType, Header... headers) {
		StringEntity requestBody = new StringEntity(body, DEFAULT_CHARSET);
		requestBody.setContentType(StringUtil.isNotBlank(contentType) ? contentType : "application/octet-stream");

		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);
		method.setEntity(requestBody);
		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse postFormData(String url, InputStream inStream, Map<String, Object> paramsMap, Header header,
											String... charsetArray) {

		String charset = charsetArray != null && charsetArray.length > 0 ? charsetArray[0] : DEFAULT_CHARSET;
		StringBuilder paramBuilder = new StringBuilder();

		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				paramBuilder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + paramBuilder.toString().substring(1);
		}


		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);
		if (null != header) {
			method.setHeader(header);
		}
		if (paramsMap != null) {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
			builder.addBinaryBody("file", inStream);
			method.setEntity(builder.build());
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			inStream.close();
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(),
						EntityUtils.toString(entity, charset));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse postFormData(String url, InputStream inStream, String fileName, Map<String, Object> paramsMap, Header header,
											String... charsetArray) {

		String charset = charsetArray != null && charsetArray.length > 0 ? charsetArray[0] : DEFAULT_CHARSET;
		StringBuilder paramBuilder = new StringBuilder();

		if (paramsMap != null) {
			for (Map.Entry<String, Object> param : paramsMap.entrySet()) {
				paramBuilder.append("&").append(param.getKey()).append("=").append(param.getValue());
			}
			url = url + "?" + paramBuilder.toString().substring(1);
		}


		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);
		if (null != header) {
			method.setHeader(header);
		}
		if (paramsMap != null) {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
//			builder.addPart("file", new FileBody(file));
			builder.addBinaryBody("file", inStream, ContentType.MULTIPART_FORM_DATA, fileName);
			method.setEntity(builder.build());
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(),
						EntityUtils.toString(entity, charset));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse postFormData(String url, Map<String, String> paramsMap, Header... headers) {
		HttpPost method = new HttpPost(url);
		method.setConfig(CONFIG);

		if (paramsMap != null) {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			for (Map.Entry<String, String> param : paramsMap.entrySet()) {
				builder.addTextBody(param.getKey(), param.getValue());
			}
			method.setEntity(builder.build());
		}
		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			Header lastHeader = response.getLastHeader("Set-Cookie");
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET), lastHeader.getValue());
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse put(String url, NameValuePair[] nameValuePair, Header... headers) {
		Map<String, String> paramsMap = new HashMap<String, String>(nameValuePair.length);
		for (NameValuePair t : nameValuePair) {
			paramsMap.put(t.getName(), t.getValue());
		}
		return put(url, paramsMap, headers);
	}

	public static HttpResponse put(String url, Map<String, String> paramsMap, Header... headers) {
		HttpPut method = new HttpPut(url);
		method.setConfig(CONFIG);

		if (paramsMap != null) {
			try {
				List<NameValuePair> paramList = new ArrayList<NameValuePair>();
				for (Map.Entry<String, String> param : paramsMap.entrySet()) {
					paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
				}
				method.setEntity(new UrlEncodedFormEntity(paramList, DEFAULT_CHARSET));
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException(e);
			}
		}
		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse put(String url, String body, String contentType, Header... headers) {
		StringEntity requestBody = new StringEntity(body, DEFAULT_CHARSET);
		requestBody.setContentType(StringUtil.isNotBlank(contentType) ? contentType : "application/octet-stream");

		HttpPut method = new HttpPut(url);
		method.setConfig(CONFIG);
		method.setEntity(requestBody);
		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static HttpResponse delete(String url, Header... headers) {
		HttpDelete method = new HttpDelete(url);
		method.setConfig(CONFIG);
		if (null != headers && headers.length > 0) {
			method.setHeaders(headers);
		}

		try (CloseableHttpResponse response = getHttpClient().execute(method)) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return new HttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, DEFAULT_CHARSET));
			}

			return new HttpResponse(response.getStatusLine().getStatusCode());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private static class IdleConnectionMonitor extends TimerTask{

		private final PoolingHttpClientConnectionManager poolingHttpClientConnectionManager;

		private IdleConnectionMonitor(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
			this.poolingHttpClientConnectionManager = poolingHttpClientConnectionManager;
		}

		@Override
		public void run() {
			// 关闭失效的连接
			poolingHttpClientConnectionManager.closeExpiredConnections();
			// 可选的, 关闭30秒内不活动的连接
			poolingHttpClientConnectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
		}
	}
}
